# -*- coding: utf-8 -*-
"""
FISCO BCOS区块链适配器
通过WeBASE-Front前置服务的HTTP API与FISCO BCOS区块链进行交互
"""

import os
import json
import requests
from typing import Dict, List, Any, Optional
from datetime import datetime

from .base_adapter import BaseBlockchainAdapter
from utils.logger import get_logger
from utils.exceptions import AdapterException, ChainException
from middleware.unified_abstraction import (
    AccountInfo, BlockInfo, TransactionInfo, ContractInfo, ChainInfo
)

logger = get_logger(__name__)

class FiscoBcosAdapter(BaseBlockchainAdapter):
    """FISCO BCOS区块链适配器"""
    
    def __init__(self, config: Dict[str, Any] = None):
        """初始化FISCO BCOS适配器"""
        if config is None:
            config = self._load_config_from_env()
        
        super().__init__(config)
        
        # 从环境变量或配置中读取参数
        self.front_host = config.get('front_host', os.environ.get('FISCO_FRONT_HOST', '47.103.91.229'))
        self.front_port = config.get('front_port', os.environ.get('FISCO_FRONT_PORT', '5002'))
        self.group_id = config.get('group_id', int(os.environ.get('FISCO_DEFAULT_GROUP_ID', '1')))
        
        # 默认合约和用户配置
        self.default_contract_address = config.get('default_contract_address', os.environ.get('FISCO_BCOS_CONTRACT_ADDRESS', '0x45a06cc767543fd15547ea75e0809a6e70234406'))
        self.default_user_address = config.get('default_user_address', os.environ.get('FISCO_BCOS_USER_ADDRESS', '0x49795c5e26929760875a9cc1416f355647d763ed'))
        self.default_contract_id = config.get('default_contract_id', int(os.environ.get('FISCO_BCOS_CONTRACT_ID', '1')))
        
        # 构建基础URL
        self.base_url = f"http://{self.front_host}:{self.front_port}/WeBASE-Front"
        
        # 请求配置
        self.session = requests.Session()
        self.session.headers.update({
            'Content-Type': 'application/json'
        })
        
        logger.info(f"Initialized FISCO BCOS adapter with base URL: {self.base_url}")
        logger.info(f"Default contract address: {self.default_contract_address}")
        logger.info(f"Default user address: {self.default_user_address}")
    
    def _load_config_from_env(self) -> Dict[str, Any]:
        """从环境变量加载配置"""
        rpc_url = os.environ.get('FISCO_BCOS_RPC_URL', 'http://47.103.91.229:5002')
        
        # 解析RPC URL获取host和port
        if '://' in rpc_url:
            protocol, rest = rpc_url.split('://', 1)
            if ':' in rest:
                host, port = rest.split(':', 1)
                port = port.split('/')[0]  # 移除路径部分
            else:
                host = rest.split('/')[0]
                port = '80' if protocol == 'http' else '443'
        else:
            host = '47.103.91.229'
            port = '5002'
        
        return {
            'front_host': host,
            'front_port': port,
            'group_id': int(os.environ.get('FISCO_BCOS_GROUP_ID', '1')),
            'chain_id': int(os.environ.get('FISCO_BCOS_CHAIN_ID', '1')),
            'node_url': rpc_url,
            'timeout': 30
        }
    
    async def connect(self) -> bool:
        """测试与Front前置服务的网络连接"""
        try:
            # 使用获取区块高度接口测试连接
            url = f"{self.base_url}/{self.group_id}/web3/blockNumber"
            response = self.session.get(url, timeout=self.timeout)
            
            if response.status_code == 200:
                self._connected = True
                logger.info(f"Successfully connected to FISCO BCOS Front service at {self.base_url}")
                return True
            else:
                self._connected = False
                logger.error(f"Failed to connect to FISCO BCOS Front service. Status: {response.status_code}")
                return False
                
        except Exception as e:
            self._connected = False
            logger.error(f"Connection test failed: {str(e)}")
            raise AdapterException(f"Failed to connect to FISCO BCOS Front service: {str(e)}")
    
    async def disconnect(self) -> bool:
        """断开连接"""
        try:
            if self.session:
                self.session.close()
            self._connected = False
            logger.info("Disconnected from FISCO BCOS Front service")
            return True
        except Exception as e:
            logger.error(f"Error during disconnect: {str(e)}")
            return False
    
    async def is_connected(self) -> bool:
        """检查连接状态"""
        return self._connected
    
    async def get_latest_block_number(self) -> int:
        """查询当前区块链的最新块高"""
        try:
            url = f"{self.base_url}/{self.group_id}/web3/blockNumber"
            response = self.session.get(url, timeout=self.timeout)
            
            if response.status_code == 200:
                block_number = int(response.text.strip())
                logger.info(f"Latest block number: {block_number}")
                return block_number
            else:
                raise ChainException(f"Failed to get block number. Status: {response.status_code}, Response: {response.text}")
                
        except ValueError as e:
            raise ChainException(f"Invalid block number response: {response.text}")
        except Exception as e:
            logger.error(f"Error getting latest block number: {str(e)}")
            raise AdapterException(f"Failed to get latest block number: {str(e)}")
    
    async def call_contract_function(self, contract_address: str, contract_name: str, 
                                   function_name: str, function_params: List[str],
                                   contract_abi: List[Dict], user_address: str = None,
                                   contract_id: int = None) -> Any:
        """执行已部署合约的函数"""
        try:
            url = f"{self.base_url}/trans/handle"
            
            # 默认用户地址
            if user_address is None:
                user_address = "0x49795c5e26929760875a9cc1416f355647d763ed"
            
            payload = {
                "groupId": str(self.group_id),
                "user": user_address,
                "contractName": contract_name,
                "funcName": function_name,
                "funcParam": function_params,
                "contractAbi": contract_abi,
                "useCns": False,
                "useAes": False,
                "cnsName": "",
                "version": "",
                "contractAddress": contract_address
            }
            
            # 如果提供了contract_id，添加到payload中
            if contract_id is not None:
                payload["contractId"] = contract_id
            
            response = self.session.post(url, json=payload, timeout=self.timeout)
            
            if response.status_code == 200:
                result = response.json()
                logger.info(f"Contract function {function_name} executed successfully")
                return result
            else:
                raise ChainException(f"Contract call failed. Status: {response.status_code}, Response: {response.text}")
                
        except Exception as e:
            logger.error(f"Error calling contract function {function_name}: {str(e)}")
            raise AdapterException(f"Failed to call contract function: {str(e)}")
    
    async def call_simple_storage_set(self, key: str, value: str,
                                    contract_address: str = None, user_address: str = None) -> Any:
        """调用SimpleStorage合约的set方法"""
        # 使用默认配置如果参数未提供
        contract_address = contract_address or self.default_contract_address
        user_address = user_address or self.default_user_address
        
        contract_abi = [{
            "constant": False,
            "inputs": [
                {"name": "key", "type": "string"},
                {"name": "value", "type": "string"}
            ],
            "name": "set",
            "outputs": [],
            "payable": False,
            "stateMutability": "nonpayable",
            "type": "function",
            "funcId": 0
        }]
        
        return await self.call_contract_function(
            contract_address=contract_address,
            contract_name="SimpleStorage",
            function_name="set",
            function_params=[key, value],
            contract_abi=contract_abi,
            user_address=user_address
        )
    
    async def call_simple_storage_get(self, key: str,
                                    contract_address: str = None, user_address: str = None, contract_id: int = None) -> Any:
        """调用SimpleStorage合约的get方法"""
        # 使用默认配置如果参数未提供
        contract_address = contract_address or self.default_contract_address
        user_address = user_address or self.default_user_address
        contract_id = contract_id or self.default_contract_id
        
        contract_abi = [{
            "constant": True,
            "inputs": [
                {"name": "key", "type": "string"}
            ],
            "name": "get",
            "outputs": [
                {"name": "", "type": "string"}
            ],
            "payable": False,
            "stateMutability": "view",
            "type": "function",
            "funcId": 1
        }]
        
        return await self.call_contract_function(
            contract_address=contract_address,
            contract_name="SimpleStorage",
            function_name="get",
            function_params=[key],
            contract_abi=contract_abi,
            user_address=user_address,
            contract_id=contract_id
        )
    
    # 实现基类的抽象方法（简化实现）
    async def get_chain_info(self) -> ChainInfo:
        """获取链信息"""
        block_number = await self.get_latest_block_number()
        return ChainInfo(
            chain_id=str(self.config.get('chain_id', 1)),
            chain_name="FISCO BCOS",
            latest_block=block_number,
            consensus="PBFT",
            version="2.9.1"
        )
    
    async def create_account(self, **kwargs) -> AccountInfo:
        """创建账户（FISCO BCOS通过Front服务管理）"""
        raise NotImplementedError("Account creation should be done through WeBASE-Front interface")
    
    async def get_account_info(self, address: str) -> AccountInfo:
        """获取账户信息"""
        # 简化实现，实际需要调用相应的Front API
        return AccountInfo(
            address=address,
            balance=0.0,
            nonce=0,
            account_type="external"
        )
    
    async def get_balance(self, address: str, token_address: str = None) -> float:
        """获取账户余额"""
        # FISCO BCOS没有原生代币概念，返回0
        return 0.0
    
    async def send_transaction(self, transaction: Dict[str, Any]) -> str:
        """发送交易"""
        try:
            url = f"{self.base_url}/trans/handle"
            
            # 构建交易请求
            payload = {
                "groupId": str(self.group_id),
                "user": transaction.get('from', '0x49795c5e26929760875a9cc1416f355647d763ed'),
                "contractName": transaction.get('contract_name', ''),
                "funcName": transaction.get('function_name', ''),
                "funcParam": transaction.get('function_params', []),
                "contractAbi": transaction.get('contract_abi', []),
                "contractAddress": transaction.get('to', ''),
                "useCns": False,
                "useAes": False,
                "version": ""
            }
            
            response = self.session.post(url, json=payload, timeout=self.timeout)
            
            if response.status_code == 200:
                result = response.json()
                tx_hash = result.get('transactionHash', '')
                logger.info(f"Transaction sent successfully with hash: {tx_hash}")
                return tx_hash
            else:
                raise ChainException(f"Transaction failed. Status: {response.status_code}, Response: {response.text}")
                
        except Exception as e:
            logger.error(f"Error sending transaction: {str(e)}")
            raise AdapterException(f"Failed to send transaction: {str(e)}")
    
    async def get_transaction(self, tx_hash: str) -> TransactionInfo:
        """获取交易信息"""
        try:
            url = f"{self.base_url}/{self.group_id}/web3/transaction/{tx_hash}"
            response = self.session.get(url, timeout=self.timeout)
            
            if response.status_code == 200:
                tx_data = response.json()
                
                return TransactionInfo(
                    tx_hash=tx_data.get('hash', tx_hash),
                    from_address=tx_data.get('from', ''),
                    to_address=tx_data.get('to', ''),
                    value=str(float(tx_data.get('value', 0))),
                    gas_limit=int(tx_data.get('gas', 0)),
                    gas_price=str(float(tx_data.get('gasPrice', 0))),
                    nonce=int(tx_data.get('nonce', 0)),
                    block_number=int(tx_data.get('blockNumber', 0)),
                    block_hash=tx_data.get('blockHash', ''),
                    status='success' if tx_data.get('status') == '0x1' else 'failed',
                    timestamp=datetime.now()  # FISCO BCOS交易中没有直接的时间戳
                )
            else:
                raise ChainException(f"Failed to get transaction. Status: {response.status_code}, Response: {response.text}")
                
        except Exception as e:
            logger.error(f"Error getting transaction {tx_hash}: {str(e)}")
            raise AdapterException(f"Failed to get transaction: {str(e)}")
    
    async def get_transaction_receipt(self, tx_hash: str) -> Dict[str, Any]:
        """获取交易回执"""
        try:
            url = f"{self.base_url}/{self.group_id}/web3/transactionReceipt/{tx_hash}"
            response = self.session.get(url, timeout=self.timeout)
            
            if response.status_code == 200:
                receipt = response.json()
                logger.info(f"Retrieved transaction receipt for {tx_hash}")
                return receipt
            else:
                raise ChainException(f"Failed to get transaction receipt. Status: {response.status_code}, Response: {response.text}")
                
        except Exception as e:
            logger.error(f"Error getting transaction receipt {tx_hash}: {str(e)}")
            raise AdapterException(f"Failed to get transaction receipt: {str(e)}")
    
    async def estimate_gas(self, transaction: Dict[str, Any]) -> int:
        """估算Gas费用"""
        return 0  # FISCO BCOS没有Gas概念
    
    async def get_latest_block(self) -> BlockInfo:
        """获取最新区块"""
        try:
            # 先获取最新区块号
            block_number = await self.get_latest_block_number()
            
            # 然后根据区块号获取完整的区块信息
            return await self.get_block_by_number(block_number)
            
        except Exception as e:
            logger.error(f"Error getting latest block: {str(e)}")
            # 如果获取完整信息失败，返回基本信息
            block_number = await self.get_latest_block_number()
            return BlockInfo(
                block_number=block_number,
                block_hash="",
                parent_hash="",
                timestamp=datetime.now(),
                transactions=[]
            )
    
    async def get_block_by_number(self, block_number: int) -> BlockInfo:
        """根据区块号获取区块"""
        try:
            url = f"{self.base_url}/{self.group_id}/web3/blockByNumber/{block_number}"
            response = self.session.get(url, timeout=self.timeout)
            
            if response.status_code == 200:
                block_data = response.json()
                
                # 解析交易列表
                transactions = []
                for tx in block_data.get('transactions', []):
                    if isinstance(tx, dict):
                        # 安全地转换十六进制值
                        def safe_hex_to_int(value, default=0):
                            if isinstance(value, str) and value.startswith('0x'):
                                try:
                                    return int(value, 16)
                                except ValueError:
                                    return default
                            elif isinstance(value, (int, float)):
                                return int(value)
                            else:
                                try:
                                    return int(value) if value else default
                                except (ValueError, TypeError):
                                    return default
                        
                        def safe_hex_to_float(value, default=0.0):
                            if isinstance(value, str) and value.startswith('0x'):
                                try:
                                    return float(int(value, 16))
                                except ValueError:
                                    return default
                            elif isinstance(value, (int, float)):
                                return float(value)
                            else:
                                try:
                                    return float(value) if value else default
                                except (ValueError, TypeError):
                                    return default
                        
                        transactions.append(TransactionInfo(
                            tx_hash=tx.get('hash', ''),
                            from_address=tx.get('from', ''),
                            to_address=tx.get('to', ''),
                            value=str(safe_hex_to_float(tx.get('value', 0))),
                            gas_limit=safe_hex_to_int(tx.get('gas', 0)),
                            gas_price=str(safe_hex_to_float(tx.get('gasPrice', 0))),
                            nonce=safe_hex_to_int(tx.get('nonce', 0)),
                            block_number=safe_hex_to_int(tx.get('blockNumber', 0)),
                            block_hash=tx.get('blockHash', ''),
                            status='success',
                            timestamp=datetime.now()
                        ))
                
                return BlockInfo(
                    block_number=int(block_data.get('number', block_number)),
                    block_hash=block_data.get('hash', ''),
                    parent_hash=block_data.get('parentHash', ''),
                    timestamp=datetime.fromtimestamp(int(block_data.get('timestamp', 0)) / 1000) if block_data.get('timestamp') else datetime.now(),
                    transactions=transactions
                )
            else:
                raise ChainException(f"Failed to get block by number. Status: {response.status_code}, Response: {response.text}")
                
        except Exception as e:
            logger.error(f"Error getting block {block_number}: {str(e)}")
            raise AdapterException(f"Failed to get block by number: {str(e)}")
    
    async def get_block_by_hash(self, block_hash: str) -> BlockInfo:
        """根据区块哈希获取区块"""
        try:
            url = f"{self.base_url}/{self.group_id}/web3/blockByHash/{block_hash}"
            response = self.session.get(url, timeout=self.timeout)
            
            if response.status_code == 200:
                block_data = response.json()
                
                # 解析交易列表
                transactions = []
                for tx in block_data.get('transactions', []):
                    if isinstance(tx, dict):
                        # 安全地转换十六进制值
                        def safe_hex_to_int(value, default=0):
                            if isinstance(value, str) and value.startswith('0x'):
                                try:
                                    return int(value, 16)
                                except ValueError:
                                    return default
                            elif isinstance(value, (int, float)):
                                return int(value)
                            else:
                                try:
                                    return int(value) if value else default
                                except (ValueError, TypeError):
                                    return default
                        
                        def safe_hex_to_float(value, default=0.0):
                            if isinstance(value, str) and value.startswith('0x'):
                                try:
                                    return float(int(value, 16))
                                except ValueError:
                                    return default
                            elif isinstance(value, (int, float)):
                                return float(value)
                            else:
                                try:
                                    return float(value) if value else default
                                except (ValueError, TypeError):
                                    return default
                        
                        transactions.append(TransactionInfo(
                            tx_hash=tx.get('hash', ''),
                            from_address=tx.get('from', ''),
                            to_address=tx.get('to', ''),
                            value=str(safe_hex_to_float(tx.get('value', 0))),
                            gas_limit=safe_hex_to_int(tx.get('gas', 0)),
                            gas_price=str(safe_hex_to_float(tx.get('gasPrice', 0))),
                            nonce=safe_hex_to_int(tx.get('nonce', 0)),
                            block_number=safe_hex_to_int(tx.get('blockNumber', 0)),
                            block_hash=tx.get('blockHash', ''),
                            status='success',
                            timestamp=datetime.now()
                        ))
                
                return BlockInfo(
                    block_number=int(block_data.get('number', 0)),
                    block_hash=block_data.get('hash', block_hash),
                    parent_hash=block_data.get('parentHash', ''),
                    timestamp=datetime.fromtimestamp(int(block_data.get('timestamp', 0)) / 1000) if block_data.get('timestamp') else datetime.now(),
                    transactions=transactions
                )
            else:
                raise ChainException(f"Failed to get block by hash. Status: {response.status_code}, Response: {response.text}")
                
        except Exception as e:
            logger.error(f"Error getting block {block_hash}: {str(e)}")
            raise AdapterException(f"Failed to get block by hash: {str(e)}")
    
    async def get_block(self, block_identifier) -> BlockInfo:
        """根据区块号或区块哈希获取区块"""
        try:
            # 如果是整数，直接作为区块号处理
            if isinstance(block_identifier, int):
                return await self.get_block_by_number(block_identifier)
            
            # 如果是字符串，判断是区块号还是区块哈希
            if isinstance(block_identifier, str):
                if block_identifier.startswith('0x') or len(block_identifier) >= 40:
                    # 区块哈希
                    return await self.get_block_by_hash(block_identifier)
                else:
                    # 区块号字符串
                    try:
                        block_number = int(block_identifier)
                        return await self.get_block_by_number(block_number)
                    except ValueError:
                        raise AdapterException(f"Invalid block identifier: {block_identifier}")
            
            raise AdapterException(f"Invalid block identifier type: {type(block_identifier)}")
        except Exception as e:
            logger.error(f"Failed to get block: {str(e)}")
            raise AdapterException(f"Failed to get block: {str(e)}")
    
    async def deploy_contract(self, contract_data: Dict[str, Any]) -> ContractInfo:
        """部署智能合约"""
        try:
            url = f"{self.base_url}/contract/deploy"
            
            # 构建部署请求
            payload = {
                "groupId": str(self.group_id),
                "user": contract_data.get('from', '0x49795c5e26929760875a9cc1416f355647d763ed'),
                "contractName": contract_data.get('contract_name', ''),
                "contractSource": contract_data.get('contract_source', ''),
                "contractAbi": contract_data.get('contract_abi', []),
                "contractBin": contract_data.get('contract_bin', ''),
                "constructorParams": contract_data.get('constructor_params', []),
                "version": contract_data.get('version', '')
            }
            
            response = self.session.post(url, json=payload, timeout=self.timeout)
            
            if response.status_code == 200:
                result = response.json()
                contract_address = result.get('contractAddress', '')
                
                logger.info(f"Contract deployed successfully at address: {contract_address}")
                
                return ContractInfo(
                    address=contract_address,
                    name=contract_data.get('contract_name', ''),
                    abi=contract_data.get('contract_abi', []),
                    bytecode=contract_data.get('contract_bin', ''),
                    deployed_at=datetime.now()
                )
            else:
                raise ChainException(f"Contract deployment failed. Status: {response.status_code}, Response: {response.text}")
                
        except Exception as e:
            logger.error(f"Error deploying contract: {str(e)}")
            raise AdapterException(f"Failed to deploy contract: {str(e)}")
    
    async def call_contract(self, contract_address: str, method: str, 
                          params: List[Any] = None, **kwargs) -> Any:
        """调用智能合约方法"""
        # 重定向到具体的合约调用方法
        if method == "set" and len(params or []) >= 2:
            return await self.call_simple_storage_set(
                key=params[0],
                value=params[1],
                contract_address=contract_address,
                user_address=kwargs.get('user_address')
            )
        elif method == "get" and len(params or []) >= 1:
            result = await self.call_simple_storage_get(
                key=params[0],
                contract_address=contract_address,
                user_address=kwargs.get('user_address'),
                contract_id=kwargs.get('contract_id')
            )
            # 确保返回完整的响应格式，而不是只返回data字段
            return result
        else:
            raise NotImplementedError(f"Method {method} not implemented")
    
    async def get_contract_info(self, contract_address: str) -> ContractInfo:
        """获取智能合约信息"""
        return ContractInfo(
            address=contract_address,
            name="SimpleStorage",
            abi=[],  # 需要从Front服务获取
            bytecode="",
            deployed_at=datetime.now()
        )
    
    async def subscribe_events(self, event_filter: Dict[str, Any], 
                             callback: callable) -> str:
        """订阅事件"""
        # FISCO BCOS 通过 WeBASE-Front 不支持实时事件订阅
        # 可以通过轮询 get_logs 方法来模拟事件订阅
        logger.warning("FISCO BCOS does not support real-time event subscription through WeBASE-Front")
        logger.info("Consider using get_logs method with periodic polling for event monitoring")
        
        # 返回一个模拟的订阅ID
        import uuid
        subscription_id = str(uuid.uuid4())
        logger.info(f"Created mock subscription with ID: {subscription_id}")
        return subscription_id
    
    async def unsubscribe_events(self, subscription_id: str) -> bool:
        """取消事件订阅"""
        # FISCO BCOS 通过 WeBASE-Front 不支持实时事件订阅
        # 这里只是记录取消订阅的操作
        logger.info(f"Mock unsubscription for subscription ID: {subscription_id}")
        return True
    
    async def get_logs(self, filter_params: Dict[str, Any]) -> List[Dict[str, Any]]:
        """获取事件日志"""
        try:
            # 尝试不同的API路径
            possible_urls = [
                f"{self.base_url}/event/eventLogs/{self.group_id}",
                f"{self.base_url}/event/logs",
                f"{self.base_url}/{self.group_id}/event/logs"
            ]
            
            # 构建日志查询参数
            params = {
                "groupId": str(self.group_id),
                "fromBlock": filter_params.get('from_block', 'latest'),
                "toBlock": filter_params.get('to_block', 'latest'),
                "addresses": filter_params.get('addresses', []),
                "topics": filter_params.get('topics', [])
            }
            
            # 尝试不同的URL
            for url in possible_urls:
                try:
                    response = self.session.post(url, json=params, timeout=self.timeout)
                    
                    if response.status_code == 200:
                        logs = response.json()
                        logger.info(f"Retrieved {len(logs)} event logs using URL: {url}")
                        return logs
                    elif response.status_code == 404:
                        continue  # 尝试下一个URL
                    else:
                        logger.warning(f"URL {url} returned status {response.status_code}")
                        continue
                except Exception as e:
                    logger.warning(f"Failed to query logs with URL {url}: {str(e)}")
                    continue
            
            # 如果所有URL都失败，返回空列表并记录警告
            logger.warning("All event log API endpoints failed, returning empty list")
            return []
                
        except Exception as e:
            logger.error(f"Error getting logs: {str(e)}")
            # 不抛出异常，返回空列表
            logger.warning("Event logs query failed, returning empty list")
            return []